package tt.dz.egobus.servicemanager.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import tt.dz.egobus.api.exception.ResponseException;
import tt.dz.egobus.cache.service.ExtendCacheService;
import tt.dz.egobus.servicemanager.bean.SpringConfigConstant;
import tt.dz.egobus.servicemanager.business.distance.*;
import tt.dz.egobus.servicemanager.business.filter.BusinessCodeFilter;
import tt.dz.egobus.servicemanager.business.filter.BusinessCodeValidateFilter;
import tt.dz.egobus.servicemanager.business.key.DriverStatus;
import tt.dz.egobus.servicemanager.business.key.KeyPackage;
import tt.dz.egobus.servicemanager.business.key.RedisPointKeyPackage;
import tt.dz.egobus.servicemanager.business.point.BusPoint;
import tt.dz.egobus.servicemanager.business.point.TransferPoint;
import tt.dz.egobus.servicemanager.core.factory.IdFactory;
import tt.dz.egobus.servicemanager.core.lock.AbstractRedisCacheLock;
import tt.dz.egobus.servicemanager.core.lock.CacheLock;
import tt.dz.egobus.servicemanager.entities.DriverRegisterBusRelative;
import tt.dz.egobus.servicemanager.service.BusinessWorkCacheService;
import tt.dz.egobus.servicemanager.vo.BusinessFinishVo;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

import static tt.dz.egobus.bean.I18NCodeEnum.RPC_PARAM_ILLEGAL_ERROR;
import static tt.dz.egobus.bean.ServiceManagerI18NCodeEnum.RPC_DRIVER_INVALIDATE_STATUS_ERROR;

/**
 * @author Anson
 * @date 17-3-23
 * @name 司机坐标服务类
 * @since 1.0.2
 */
@Service("redisBusinessWorkCacheServiceImpl")
public class RedisBusinessWorkCacheServiceImpl implements BusinessWorkCacheService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisBusinessWorkCacheServiceImpl.class);

    /**
     * 由系统限制显示范围
     */
    private static final double LIMIT_BY_SYSTEM = 0D;


    public static final String DRIVER_CACHE_PRE = "EGOBUS_DRIVER_";


    @Autowired
    @Qualifier("extendsRedisServiceImpl")
    ExtendCacheService cacheService;

    @Autowired
    @Qualifier("springConfigConstant")
    SpringConfigConstant springConfigConstant;

    /**
     * 队列算法
     */
    IdFactory<DistanceAlgorithm> distanceAlgorithmFactory = new SPIDistanceAlgorithmFactory();

    /**
     * 历史坐标计算距离计算
     */
    IdFactory<TransferPointValidation> transferFactory = new SPITransferPointValidationFactory();

    /**
     * 设置当前历史记录实现类
     */
    TransferPoint transferPoint = new RedisCacheTransferPoint();

    /**
     * 排除businessCode空的
     */
    PointFilter businsessCodeNullFilter = new BusinessCodeFilter();


    /**
     * @param driverId 司机id
     * @param status   预先更新的状态
     * @author Anson
     * @date 17-3-27
     * @name
     * @since 1.0.2
     */
    public DriverStatus updateStatus(String driverId, DriverRegisterBusRelative.RelativeStatus status) {
        DriverStatus driverStatus = this.getStatus(driverId);
        if (driverStatus == null) {
            driverStatus = new DriverStatus();
        }
        driverStatus.setStatus(status);
        driverStatus.setCacheKey(this.driverStatusKey(driverId, status));
        this.updateDriverStatusCache(driverStatus, driverId);
        return driverStatus;
    }

    /**
     * @param driverStatus 司机id
     * @param status       预先更新的状态
     * @author Anson
     * @date 17-3-27
     * @name
     * @since 1.0.2
     */
    public DriverStatus updateStatus(DriverStatus driverStatus, String driverId,
                                     DriverRegisterBusRelative.RelativeStatus status) {
        if (driverStatus == null) {
            return null;
        }
        driverStatus.setStatus(status);
        driverStatus.setCacheKey(this.driverStatusKey(driverId, status));
        this.updateDriverStatusCache(driverStatus, driverId);
        return driverStatus;
    }


    /**
     * @param driverId 司机id
     * @author Anson
     * @date 17-3-27
     * @name 删除司机端状态
     * @since 1.0.2
     */
    public void deleteStatus(String driverId) {
        cacheService.del(getDriverStatusKey(driverId));
    }


    /**
     * @param driverId 司机key
     * @author Anson
     * @date 17-3-27
     * @name
     * @since 1.0.3
     */
    @Override
    public DriverStatus getStatus(String driverId) {
        String json = cacheService.get(getDriverStatusKey(driverId));
        return json == null ? null : DriverStatus.toBean(json);
    }

    /**
     * @param driverId      司机id
     * @param businessCodes 当前车辆的有效服务号
     * @author Anson
     * @date 17-3-27
     * @name 更新当前司机的状态
     * @since 1.0.3
     */
    @Override
    public void active(String driverId, int[] businessCodes) {
        DriverStatus status = this.updateStatus(driverId, DriverRegisterBusRelative.RelativeStatus.ACTIVE);
        BusPoint busPoint = getNowPoint(driverId);
        double x = 0D;
        double y = 0D;
        if (busPoint != null) {
            x = busPoint.x();
            y = busPoint.y();
        }
        busPoint = new BusPoint(driverId, x, y, businessCodes);
        this.cacheService.set(status.getCacheKey(), busPoint.toJson());
    }

    /**
     * @param driverId 司机id
     * @author Anson
     * @date 17-3-27
     * @name 获取当前的司机状态，若存在当前司机状态，则删除当前司机相关缓存
     * @since 1.0.3
     */
    @Override
    public void down(String driverId) {
        DriverStatus busStatus = this.getStatus(driverId);
        if (busStatus == null) {
            return;
        }
        this.deleteStatus(driverId);
        this.cacheService.del(busStatus.getCacheKey());
    }

    /**
     * @param driverId 司机id
     * @param x
     * @param y
     * @author Anson
     * @date 17-3-27
     * @name 获取当前的司机状态，若存在当前司机状态，更新当前地理位置，若不存在则设置司机状态为未激活
     * @since 1.0.3
     */
    @Override
    public void updatePoint(String driverId, double x, double y) {
        DriverStatus busStatus = this.getStatus(driverId);
        if (busStatus == null) {
            busStatus = this.updateStatus(driverId, DriverRegisterBusRelative.RelativeStatus.UN_ACTIVE);
            BusPoint busPoint = new BusPoint(driverId, 0, 0, null);
            this.cacheService.set(busStatus.getCacheKey(), busPoint.toJson());
        }

        String key = busStatus.getCacheKey();
        BusPoint busPoint = BusPoint.toBean(this.cacheService.get(key));
        if (busPoint == null) {
            return;
        }
        busPoint.setX(x);
        busPoint.setY(y);
        this.cacheService.set(key, busPoint.toJson());

        if (this.springConfigConstant.isStoreBusinessStatusPoint()) {
            this.transferPoint.uploadPoint(driverId, busPoint);
        }

    }

    /**
     * @param x
     * @param y
     * @param maxDistanceLimit 非必须，当前限制最大距离单位m,米 (0:表示由系统限制,-1:表示不限制,>0表示限制)
     * @param status           非必须 查询当前状态,null,则全查询
     * @author Anson
     * @date 17-3-24
     * @name 获取当前坐标点下的可显示车点位置
     * 先进行最大距离的算法处理，并且进行算法过滤，采用jdk的sfi服务协议
     * @since 1.0.2
     */
    @Override
    public BusPoint[] filterMaxLength(double x, double y, double maxDistanceLimit,
                                      DriverRegisterBusRelative.RelativeStatus status) {
        KeyPackage keyPackage = this.keyPackage("", status);
        String filterMaxLengthCacheKey = keyPackage.key() + "*";
        LOGGER.debug("过滤有效的激活车状态缓存key={}", filterMaxLengthCacheKey);
        Set<String> busPointJson = this.cacheService.getValueByKeysPatterns(filterMaxLengthCacheKey);
        double limit = 0;
        if (LIMIT_BY_SYSTEM == maxDistanceLimit) {
            limit = this.springConfigConstant.getSearchMax();
        } else {
            limit = maxDistanceLimit;
        }
        if (maxDistanceLimit > 0) {
            limit = maxDistanceLimit;
        }
        if (busPointJson != null && busPointJson.size() > 0) {
            List<BusPoint> busPoints = BusPoint.toListBean(busPointJson);
            if (busPoints != null && !busPoints.isEmpty()) {
                DistanceAlgorithm distanceAlgorithm =
                        this.distanceAlgorithmFactory.factory(DistanceAlgorithmFactory.DEFAULT_ALGORITHM_ID);
                distanceAlgorithm.setMaxDistanceLimit(limit);
                distanceAlgorithm.setCurrentPoint(x, y);
                distanceAlgorithm.setBusPoint(busPoints);
                return distanceAlgorithm.getFilter(this.businsessCodeNullFilter);
            }
        }
        return new BusPoint[0];
    }

    @Override
    public BusPoint[] filterMaxLength(double x, double y, PointFilter pointFilter,
                                      DriverRegisterBusRelative.RelativeStatus status) {
        KeyPackage keyPackage = this.keyPackage("", status);
        String filterMaxLengthCacheKey = keyPackage.key() + "*";
        LOGGER.info("过滤有效的激活车状态缓存key={}", filterMaxLengthCacheKey);
        Set<String> busPointJson = this.cacheService.getValueByKeysPatterns(filterMaxLengthCacheKey);
        if (busPointJson != null && busPointJson.size() > 0) {
            LOGGER.info("过滤有效的激活车状态缓存key={},有效的司机个数为{}", filterMaxLengthCacheKey, busPointJson.size());
            List<BusPoint> busPoints = BusPoint.toListBean(busPointJson);
            if (busPoints != null && !busPoints.isEmpty()) {
                DistanceAlgorithm distanceAlgorithm =
                        this.distanceAlgorithmFactory.factory(DistanceAlgorithmFactory.DEFAULT_ALGORITHM_ID);
                LOGGER.info("过滤有效的司机个数为{},反序列化对象={},x={},y={},status={},distanceAlgorithm={}",
                        busPointJson.size(), busPoints.size(), x, y, status.getCode(), distanceAlgorithm);
                distanceAlgorithm.setCurrentPoint(x, y);
                distanceAlgorithm.setBusPoint(busPoints);
                return distanceAlgorithm.getFilter(pointFilter);
            }
        }
        return new BusPoint[0];
    }


    @Override
    public BusPoint[] bestSearch(double x, double y, int businessCode, String algorithmId) throws ResponseException {
        //当前已经对有效范围进行了过滤,最大距离以及有效状态，当前只是在这些返回进行排序
        BusPoint[] busPoints = this.filterMaxLength(x, y, LIMIT_BY_SYSTEM,
                DriverRegisterBusRelative.RelativeStatus.ACTIVE);
        return this.bestSearch(busPoints, x, y, algorithmId, new BusinessCodeValidateFilter(businessCode));
    }

    @Override
    public BusPoint[] bestSearch(BusPoint[] availableBusPoints, double x, double y, String algorithmId) throws ResponseException {
        return this.bestSearch(availableBusPoints, x, y, algorithmId, null);
    }

    @Override
    public BusPoint[] bestSearch(BusPoint[] availableBusPoints, double x, double y,
                                 String algorithmId, PointFilter pointFilter) throws ResponseException {
        algorithmId = StringUtils.isEmpty(algorithmId) ? DistanceAlgorithmFactory.DEFAULT_ALGORITHM_ID : algorithmId;
        //当前已经对有效范围进行了过滤,最大距离以及有效状态，当前只是在这些返回进行排序
        BusPoint[] busPoints = availableBusPoints;
        if (busPoints != null && busPoints.length > 0) {
            DistanceAlgorithm distanceAlgorithm = this.distanceAlgorithmFactory.factory(algorithmId);
            if (distanceAlgorithm == null) {
                throw new ResponseException(RPC_PARAM_ILLEGAL_ERROR);
            }
            //已经做了过滤,故当前这个最大距离，不用限制
            distanceAlgorithm.setMaxDistanceLimit(DefaultDistanceAlgorithm.UN_LIMIT);
            distanceAlgorithm.setCurrentPoint(x, y);
            distanceAlgorithm.setMaxQueueLimit(this.springConfigConstant.getQueueLimit());
            distanceAlgorithm.setBusPoint(Arrays.asList(busPoints));
            return distanceAlgorithm.bestQueue(pointFilter);
        }
        return new BusPoint[0];
    }

    @Override
    public BusPoint getNowPoint(String driverId) {
        DriverStatus busStatus = this.getStatus(driverId);
        if (busStatus == null) {
            return null;
        }
        String key = busStatus.getCacheKey();
        String json = this.cacheService.get(key);
        return BusPoint.toBean(json);
    }

    /**
     * @param driverId
     * @author Anson
     * @date 17-3-27
     * @name 先获取当前的司机端的状态缓存，判断当前混村是否存在
     * 若存在，则更新当前状态为{@link DriverRegisterBusRelative.RelativeStatus#BEGIN_BUSINESS}，反之不处理
     * 状态更新则代表point的缓存key也的更新
     * @since 1.0.3
     */
    @Override
    public boolean doDriverBeginBusiness(String driverId) throws ResponseException {
        if (StringUtils.isEmpty(driverId)) {
            throw new ResponseException(RPC_PARAM_ILLEGAL_ERROR);
        }
        CacheLock lock = new RedisCacheLock(this.cacheService, driverId);
        try {
            int count = 100;
            boolean isLock = false;
            while (((--count) > 0) && !(isLock = lock.aquireLock())) {
                //小于100次并且当前并未获取到锁的时候
                //休眠10毫秒在请求
                Thread.sleep(10);
            }
            if (!isLock) {
                LOGGER.error("当前获取司机并开始行程获取并发锁失败,driverId={}", driverId);
                return false;
            }
            LOGGER.info("当前获取司机并开始行程获取并发锁成功,driverId={}", driverId);
            DriverStatus busStatus = this.getStatus(driverId);
            if (busStatus == null) {
                return false;
            }
            //再次确认下当前司机状态是否是处于激活的状态
            //若多线程下，当前的司机很有可能被别的线程所修改，则当前先的状态可能不是预先的结果
            if (!DriverRegisterBusRelative.RelativeStatus.assertActive(busStatus.getStatus())) {
                throw new ResponseException(RPC_DRIVER_INVALIDATE_STATUS_ERROR);
            }
            String oldKey = busStatus.getCacheKey();
            busStatus = this.updateStatus(busStatus, driverId, DriverRegisterBusRelative.RelativeStatus.BEGIN_BUSINESS);
            String key = busStatus.getCacheKey();
            this.updateKey(oldKey, key, true);
            return true;
        } catch (Exception e) {
            LOGGER.error("司机多线程下，裂变状态时候的异常", e);
            return false;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean doTask(String driverId) throws ResponseException {
        if (StringUtils.isEmpty(driverId)) {
            throw new ResponseException(RPC_PARAM_ILLEGAL_ERROR);
        }
        DriverStatus busStatus = this.getStatus(driverId);
        if (busStatus == null) {
            return false;
        }
        //确认当前司机的状态为开始
        if (!DriverRegisterBusRelative.RelativeStatus.assertBeginBusiness(busStatus.getStatus())) {
            throw new ResponseException(RPC_DRIVER_INVALIDATE_STATUS_ERROR);
        }
        String oldKey = busStatus.getCacheKey();
        busStatus = this.updateStatus(busStatus, driverId, DriverRegisterBusRelative.RelativeStatus.BUSINESS);
        String key = busStatus.getCacheKey();
        this.updateKey(oldKey, key, true);
        return true;
    }

    @Override
    public boolean doFinish(BusinessFinishVo finishVo) throws ResponseException {
        String driverId = finishVo.getDriverId();
        if (StringUtils.isEmpty(driverId)) {
            throw new ResponseException(RPC_PARAM_ILLEGAL_ERROR);
        }
        DriverStatus busStatus = this.getStatus(driverId);
        if (busStatus == null) {
            return false;
        }
        //确认当前司机的状态为接单任务中
        if (!DriverRegisterBusRelative.RelativeStatus.assertBusiness(busStatus.getStatus())) {
            throw new ResponseException(RPC_DRIVER_INVALIDATE_STATUS_ERROR);
        }
        busStatus.setBusinessId("");
        String oldKey = busStatus.getCacheKey();
        busStatus = this.updateStatus(busStatus, driverId, DriverRegisterBusRelative.RelativeStatus.ACTIVE);
        String key = busStatus.getCacheKey();
        this.updateKey(oldKey, key, true);
        return true;
    }

    @Override
    public boolean doUserCancel(String driverId) throws ResponseException {
        if (StringUtils.isEmpty(driverId)) {
            throw new ResponseException(RPC_PARAM_ILLEGAL_ERROR);
        }
        DriverStatus busStatus = this.getStatus(driverId);
        if (busStatus == null) {
            return false;
        }
        //确认当前司机的状态为接单任务中
        if (DriverRegisterBusRelative.RelativeStatus.assertBusiness(busStatus.getStatus())) {
            LOGGER.error("用户取消Service--> 当前司机状态处于doBusiness状态,status={}", busStatus);
            throw new ResponseException(RPC_DRIVER_INVALIDATE_STATUS_ERROR);
        }
        String oldKey = busStatus.getCacheKey();
        busStatus.setBusinessId("");
        busStatus = this.updateStatus(busStatus, driverId, DriverRegisterBusRelative.RelativeStatus.ACTIVE);
        String key = busStatus.getCacheKey();
        this.updateKey(oldKey, key, true);
        return true;
    }

    @Override
    public double validateDistance(BusinessFinishVo finishVo) throws ResponseException {
        if (StringUtils.isEmpty(finishVo.getDriverId()) || !this.springConfigConstant.isStoreBusinessStatusPoint()) {
            return finishVo.getDriverAccDistance();
        }
        TransferPointValidation validation = this.transferFactory.factory(this.springConfigConstant.getValidateAlgorithmId());
        validation.setPointContext(this.transferPoint);
        return validation.validate(finishVo.getDriverId(), finishVo.getDistance(), finishVo.getDriverAccDistance());
    }

    @Override
    public boolean doException(String driverId) {
        this.down(driverId);
        return true;
    }

    void updateDriverStatusCache(DriverStatus driverStatus, String driverId) {
        String key = getDriverStatusKey(driverId);
        this.cacheService.set(key, driverStatus.toJson());
    }

    /**
     * @param driverId 司机id
     * @author Anson
     * @date 17-3-23
     * @name 获取司机的主键
     * @since 1.0.2
     */
    String driverKey(String driverId) {
        KeyPackage keyPackage = this.keyPackage(driverId);
        return keyPackage.key();
    }

    KeyPackage keyPackage(String driverId) {
        KeyPackage keyPackage = new RedisPointKeyPackage(driverId);
        keyPackage.setPrefix(this.springConfigConstant.getKeyPrefix());
        return keyPackage;
    }

    String driverStatusKey(String driverId, DriverRegisterBusRelative.RelativeStatus status) {
        KeyPackage keyPackage = this.keyPackage(driverId, status);
        return keyPackage.key();
    }

    KeyPackage keyPackage(String driverId, DriverRegisterBusRelative.RelativeStatus status) {
        KeyPackage keyPackage = new RedisPointKeyPackage(driverId, status);
        keyPackage.setPrefix(this.springConfigConstant.getKeyPrefix());
        return keyPackage;
    }

    /**
     * @param oldKey      原始key
     * @param newKey      新的key
     * @param isDelOldKey 是否删除旧的key
     * @author Anson
     * @date 17-3-28
     * @name key的更新
     * @since 1.0.3
     */
    String updateKey(String oldKey, String newKey, boolean isDelOldKey) {
        String json = this.cacheService.get(oldKey);
        if (isDelOldKey) {
            this.cacheService.del(oldKey);
        }
        this.cacheService.set(newKey, json);
        return newKey;
    }


    /**
     * @param driverId 司机id
     * @author Anson
     * @date 17-3-27
     * @name 拼接司机端状态的缓存前缀
     * @since 1.0.3
     */
    String getDriverStatusKey(String driverId) {
        return DRIVER_CACHE_PRE + driverId;
    }

    public static class RedisCacheLock extends AbstractRedisCacheLock {

        public static final String DRIVER_LOCK_PRE = "EGOBUS_LOCK_DRIVER_";

        private final ExtendCacheService cacheService;

        private final String lockKey;


        public RedisCacheLock(ExtendCacheService cacheService, String driverId) {
            this.cacheService = cacheService;
            this.lockKey = getLockKey(driverId);
        }

        @Override
        protected ExtendCacheService cacheService() {
            return this.cacheService;
        }

        @Override
        protected String getLockKey() {
            return lockKey;
        }

        private String getLockKey(String driver) {
            return DRIVER_LOCK_PRE + driver;
        }


    }
}
